/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.

This file is part of Quake III Arena source code.

Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

/*****************************************************************************
 * name:		be_ai_char.c
 *
 * desc:		bot characters
 *
 * $Archive: /code/botlib/be_ai_char.c $
 *
 *****************************************************************************/

#include "../game/q_shared.h"
#include "l_log.h"
#include "l_memory.h"
#include "l_utils.h"
#include "l_script.h"
#include "l_precomp.h"
#include "l_struct.h"
#include "l_libvar.h"
#include "aasfile.h"
#include "../game/botlib.h"
#include "../game/be_aas.h"
#include "be_aas_funcs.h"
#include "be_interface.h"
#include "../game/be_ai_char.h"

#define MAX_CHARACTERISTICS		80

#define CT_INTEGER				1
#define CT_FLOAT				2
#define CT_STRING				3

#define DEFAULT_CHARACTER		"bots/default_c.c"

//characteristic value
union cvalue
{
    int integer;
    float _float;
    char *string;
};
//a characteristic
typedef struct bot_characteristic_s
{
    char type;						//characteristic type
    union cvalue value;				//characteristic value
} bot_characteristic_t;

//a bot character
typedef struct bot_character_s
{
    char filename[MAX_QPATH];
    float skill;
    bot_characteristic_t c[1];		//variable sized
} bot_character_t;

bot_character_t *botcharacters[MAX_CLIENTS + 1];

//========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//========================================================================
bot_character_t *BotCharacterFromHandle(int handle)
{
    if (handle <= 0 || handle > MAX_CLIENTS)
    {
        botimport.Print(PRT_FATAL, "character handle %d out of range\n", handle);
        return NULL;
    } //end if
    if (!botcharacters[handle])
    {
        botimport.Print(PRT_FATAL, "invalid character %d\n", handle);
        return NULL;
    } //end if
    return botcharacters[handle];
} //end of the function BotCharacterFromHandle
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
void BotDumpCharacter(bot_character_t *ch)
{
    int i;

    Log_Write("%s", ch->filename);
    Log_Write("skill %d\n", ch->skill);
    Log_Write("{\n");
    for (i = 0; i < MAX_CHARACTERISTICS; i++)
    {
        switch(ch->c[i].type)
        {
        case CT_INTEGER: Log_Write(" %4d %d\n", i, ch->c[i].value.integer); break;
        case CT_FLOAT: Log_Write(" %4d %f\n", i, ch->c[i].value._float); break;
        case CT_STRING: Log_Write(" %4d %s\n", i, ch->c[i].value.string); break;
        } //end case
    } //end for
    Log_Write("}\n");
} //end of the function BotDumpCharacter
//========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//========================================================================
void BotFreeCharacterStrings(bot_character_t *ch)
{
    int i;

    for (i = 0; i < MAX_CHARACTERISTICS; i++)
    {
        if (ch->c[i].type == CT_STRING)
        {
            FreeMemory(ch->c[i].value.string);
        } //end if
    } //end for
} //end of the function BotFreeCharacterStrings
//========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//========================================================================
void BotFreeCharacter2(int handle)
{
    if (handle <= 0 || handle > MAX_CLIENTS)
    {
        botimport.Print(PRT_FATAL, "character handle %d out of range\n", handle);
        return;
    } //end if
    if (!botcharacters[handle])
    {
        botimport.Print(PRT_FATAL, "invalid character %d\n", handle);
        return;
    } //end if
    BotFreeCharacterStrings(botcharacters[handle]);
    FreeMemory(botcharacters[handle]);
    botcharacters[handle] = NULL;
} //end of the function BotFreeCharacter2
//========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//========================================================================
void BotFreeCharacter(int handle)
{
    if (!LibVarGetValue("bot_reloadcharacters")) return;
    BotFreeCharacter2(handle);
} //end of the function BotFreeCharacter
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
void BotDefaultCharacteristics(bot_character_t *ch, bot_character_t *defaultch)
{
    int i;

    for (i = 0; i < MAX_CHARACTERISTICS; i++)
    {
        if (ch->c[i].type) continue;
        //
        if (defaultch->c[i].type == CT_FLOAT)
        {
            ch->c[i].type = CT_FLOAT;
            ch->c[i].value._float = defaultch->c[i].value._float;
        } //end if
        else if (defaultch->c[i].type == CT_INTEGER)
        {
            ch->c[i].type = CT_INTEGER;
            ch->c[i].value.integer = defaultch->c[i].value.integer;
        } //end else if
        else if (defaultch->c[i].type == CT_STRING)
        {
            ch->c[i].type = CT_STRING;
            ch->c[i].value.string = (char *) GetMemory(strlen(defaultch->c[i].value.string)+1);
            strcpy(ch->c[i].value.string, defaultch->c[i].value.string);
        } //end else if
    } //end for
} //end of the function BotDefaultCharacteristics
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
bot_character_t *BotLoadCharacterFromFile(char *charfile, int skill)
{
    int indent, index, foundcharacter;
    bot_character_t *ch;
    source_t *source;
    token_t token;

    foundcharacter = qfalse;
    //a bot character is parsed in two phases
    PC_SetBaseFolder(BOTFILESBASEFOLDER);
    source = LoadSourceFile(charfile);
    if (!source)
    {
        botimport.Print(PRT_ERROR, "counldn't load %s\n", charfile);
        return NULL;
    } //end if
    ch = (bot_character_t *) GetClearedMemory(sizeof(bot_character_t) +
            MAX_CHARACTERISTICS * sizeof(bot_characteristic_t));
    strcpy(ch->filename, charfile);
    while(PC_ReadToken(source, &token))
    {
        if (!strcmp(token.string, "skill"))
        {
            if (!PC_ExpectTokenType(source, TT_NUMBER, 0, &token))
            {
                FreeSource(source);
                BotFreeCharacterStrings(ch);
                FreeMemory(ch);
                return NULL;
            } //end if
            if (!PC_ExpectTokenString(source, "{"))
            {
                FreeSource(source);
                BotFreeCharacterStrings(ch);
                FreeMemory(ch);
                return NULL;
            } //end if
            //if it's the correct skill
            if (skill < 0 || (int)token.intvalue == skill)
            {
                foundcharacter = qtrue;
                ch->skill = token.intvalue;
                while(PC_ExpectAnyToken(source, &token))
                {
                    if (!strcmp(token.string, "}")) break;
                    if (token.type != TT_NUMBER || !(token.subtype & TT_INTEGER))
                    {
                        SourceError(source, "expected integer index, found %s\n", token.string);
                        FreeSource(source);
                        BotFreeCharacterStrings(ch);
                        FreeMemory(ch);
                        return NULL;
                    } //end if
                    index = token.intvalue;
                    if (index < 0 || index > MAX_CHARACTERISTICS)
                    {
                        SourceError(source, "characteristic index out of range [0, %d]\n", MAX_CHARACTERISTICS);
                        FreeSource(source);
                        BotFreeCharacterStrings(ch);
                        FreeMemory(ch);
                        return NULL;
                    } //end if
                    if (ch->c[index].type)
                    {
                        SourceError(source, "characteristic %d already initialized\n", index);
                        FreeSource(source);
                        BotFreeCharacterStrings(ch);
                        FreeMemory(ch);
                        return NULL;
                    } //end if
                    if (!PC_ExpectAnyToken(source, &token))
                    {
                        FreeSource(source);
                        BotFreeCharacterStrings(ch);
                        FreeMemory(ch);
                        return NULL;
                    } //end if
                    if (token.type == TT_NUMBER)
                    {
                        if (token.subtype & TT_FLOAT)
                        {
                            ch->c[index].value._float = token.floatvalue;
                            ch->c[index].type = CT_FLOAT;
                        } //end if
                        else
                        {
                            ch->c[index].value.integer = token.intvalue;
                            ch->c[index].type = CT_INTEGER;
                        } //end else
                    } //end if
                    else if (token.type == TT_STRING)
                    {
                        StripDoubleQuotes(token.string);
                        ch->c[index].value.string = (char *)GetMemory(strlen(token.string)+1);
                        strcpy(ch->c[index].value.string, token.string);
                        ch->c[index].type = CT_STRING;
                    } //end else if
                    else
                    {
                        SourceError(source, "expected integer, float or string, found %s\n", token.string);
                        FreeSource(source);
                        BotFreeCharacterStrings(ch);
                        FreeMemory(ch);
                        return NULL;
                    } //end else
                } //end if
                break;
            } //end if
            else
            {
                indent = 1;
                while(indent)
                {
                    if (!PC_ExpectAnyToken(source, &token))
                    {
                        FreeSource(source);
                        BotFreeCharacterStrings(ch);
                        FreeMemory(ch);
                        return NULL;
                    } //end if
                    if (!strcmp(token.string, "{")) indent++;
                    else if (!strcmp(token.string, "}")) indent--;
                } //end while
            } //end else
        } //end if
        else
        {
            SourceError(source, "unknown definition %s\n", token.string);
            FreeSource(source);
            BotFreeCharacterStrings(ch);
            FreeMemory(ch);
            return NULL;
        } //end else
    } //end while
    FreeSource(source);
    //
    if (!foundcharacter)
    {
        BotFreeCharacterStrings(ch);
        FreeMemory(ch);
        return NULL;
    } //end if
    return ch;
} //end of the function BotLoadCharacterFromFile
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotFindCachedCharacter(char *charfile, float skill)
{
    int handle;

    for (handle = 1; handle <= MAX_CLIENTS; handle++)
    {
        if ( !botcharacters[handle] ) continue;
        if ( strcmp( botcharacters[handle]->filename, charfile ) == 0 &&
                (skill < 0 || fabs(botcharacters[handle]->skill - skill) < 0.01) )
        {
            return handle;
        } //end if
    } //end for
    return 0;
} //end of the function BotFindCachedCharacter
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotLoadCachedCharacter(char *charfile, float skill, int reload)
{
    int handle, cachedhandle, intskill;
    bot_character_t *ch = NULL;
#ifdef DEBUG
    int starttime;

    starttime = Sys_MilliSeconds();
#endif //DEBUG

    //find a free spot for a character
    for (handle = 1; handle <= MAX_CLIENTS; handle++)
    {
        if (!botcharacters[handle]) break;
    } //end for
    if (handle > MAX_CLIENTS) return 0;
    //try to load a cached character with the given skill
    if (!reload)
    {
        cachedhandle = BotFindCachedCharacter(charfile, skill);
        if (cachedhandle)
        {
            botimport.Print(PRT_MESSAGE, "loaded cached skill %f from %s\n", skill, charfile);
            return cachedhandle;
        } //end if
    } //end else
    //
    intskill = (int) (skill + 0.5);
    //try to load the character with the given skill
    ch = BotLoadCharacterFromFile(charfile, intskill);
    if (ch)
    {
        botcharacters[handle] = ch;
        //
        botimport.Print(PRT_MESSAGE, "loaded skill %d from %s\n", intskill, charfile);
#ifdef DEBUG
        if (bot_developer->integer)
        {
            botimport.Print(PRT_MESSAGE, "skill %d loaded in %d msec from %s\n", intskill, Sys_MilliSeconds() - starttime, charfile);
        } //end if
#endif //DEBUG
        return handle;
    } //end if
    //
    botimport.Print(PRT_WARNING, "couldn't find skill %d in %s\n", intskill, charfile);
    //
    if (!reload)
    {
        //try to load a cached default character with the given skill
        cachedhandle = BotFindCachedCharacter(DEFAULT_CHARACTER, skill);
        if (cachedhandle)
        {
            botimport.Print(PRT_MESSAGE, "loaded cached default skill %d from %s\n", intskill, charfile);
            return cachedhandle;
        } //end if
    } //end if
    //try to load the default character with the given skill
    ch = BotLoadCharacterFromFile(DEFAULT_CHARACTER, intskill);
    if (ch)
    {
        botcharacters[handle] = ch;
        botimport.Print(PRT_MESSAGE, "loaded default skill %d from %s\n", intskill, charfile);
        return handle;
    } //end if
    //
    if (!reload)
    {
        //try to load a cached character with any skill
        cachedhandle = BotFindCachedCharacter(charfile, -1);
        if (cachedhandle)
        {
            botimport.Print(PRT_MESSAGE, "loaded cached skill %f from %s\n", botcharacters[cachedhandle]->skill, charfile);
            return cachedhandle;
        } //end if
    } //end if
    //try to load a character with any skill
    ch = BotLoadCharacterFromFile(charfile, -1);
    if (ch)
    {
        botcharacters[handle] = ch;
        botimport.Print(PRT_MESSAGE, "loaded skill %f from %s\n", ch->skill, charfile);
        return handle;
    } //end if
    //
    if (!reload)
    {
        //try to load a cached character with any skill
        cachedhandle = BotFindCachedCharacter(DEFAULT_CHARACTER, -1);
        if (cachedhandle)
        {
            botimport.Print(PRT_MESSAGE, "loaded cached default skill %f from %s\n", botcharacters[cachedhandle]->skill, charfile);
            return cachedhandle;
        } //end if
    } //end if
    //try to load a character with any skill
    ch = BotLoadCharacterFromFile(DEFAULT_CHARACTER, -1);
    if (ch)
    {
        botcharacters[handle] = ch;
        botimport.Print(PRT_MESSAGE, "loaded default skill %f from %s\n", ch->skill, charfile);
        return handle;
    } //end if
    //
    botimport.Print(PRT_WARNING, "couldn't load any skill from %s\n", charfile);
    //couldn't load any character
    return 0;
} //end of the function BotLoadCachedCharacter
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotLoadCharacterSkill(char *charfile, float skill)
{
    int ch, defaultch;

    defaultch = BotLoadCachedCharacter(DEFAULT_CHARACTER, skill, qfalse);
    ch = BotLoadCachedCharacter(charfile, skill, (int)LibVarGetValue("bot_reloadcharacters"));

    if (defaultch && ch)
    {
        BotDefaultCharacteristics(botcharacters[ch], botcharacters[defaultch]);
    } //end if

    return ch;
} //end of the function BotLoadCharacterSkill
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotInterpolateCharacters(int handle1, int handle2, float desiredskill)
{
    bot_character_t *ch1, *ch2, *out;
    int i, handle;
    float scale;

    ch1 = BotCharacterFromHandle(handle1);
    ch2 = BotCharacterFromHandle(handle2);
    if (!ch1 || !ch2)
        return 0;
    //find a free spot for a character
    for (handle = 1; handle <= MAX_CLIENTS; handle++)
    {
        if (!botcharacters[handle]) break;
    } //end for
    if (handle > MAX_CLIENTS) return 0;
    out = (bot_character_t *) GetClearedMemory(sizeof(bot_character_t) +
            MAX_CHARACTERISTICS * sizeof(bot_characteristic_t));
    out->skill = desiredskill;
    strcpy(out->filename, ch1->filename);
    botcharacters[handle] = out;

    scale = (float) (desiredskill - ch1->skill) / (ch2->skill - ch1->skill);
    for (i = 0; i < MAX_CHARACTERISTICS; i++)
    {
        //
        if (ch1->c[i].type == CT_FLOAT && ch2->c[i].type == CT_FLOAT)
        {
            out->c[i].type = CT_FLOAT;
            out->c[i].value._float = ch1->c[i].value._float +
                                     (ch2->c[i].value._float - ch1->c[i].value._float) * scale;
        } //end if
        else if (ch1->c[i].type == CT_INTEGER)
        {
            out->c[i].type = CT_INTEGER;
            out->c[i].value.integer = ch1->c[i].value.integer;
        } //end else if
        else if (ch1->c[i].type == CT_STRING)
        {
            out->c[i].type = CT_STRING;
            out->c[i].value.string = (char *) GetMemory(strlen(ch1->c[i].value.string)+1);
            strcpy(out->c[i].value.string, ch1->c[i].value.string);
        } //end else if
    } //end for
    return handle;
} //end of the function BotInterpolateCharacters
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotLoadCharacter(char *charfile, float skill)
{
    int firstskill, secondskill, handle;

    //make sure the skill is in the valid range
    if (skill < 1.0) skill = 1.0;
    else if (skill > 5.0) skill = 5.0;
    //skill 1, 4 and 5 should be available in the character files
    if (skill == 1.0 || skill == 4.0 || skill == 5.0)
    {
        return BotLoadCharacterSkill(charfile, skill);
    } //end if
    //check if there's a cached skill
    handle = BotFindCachedCharacter(charfile, skill);
    if (handle)
    {
        botimport.Print(PRT_MESSAGE, "loaded cached skill %f from %s\n", skill, charfile);
        return handle;
    } //end if
    if (skill < 4.0)
    {
        //load skill 1 and 4
        firstskill = BotLoadCharacterSkill(charfile, 1);
        if (!firstskill) return 0;
        secondskill = BotLoadCharacterSkill(charfile, 4);
        if (!secondskill) return firstskill;
    } //end if
    else
    {
        //load skill 4 and 5
        firstskill = BotLoadCharacterSkill(charfile, 4);
        if (!firstskill) return 0;
        secondskill = BotLoadCharacterSkill(charfile, 5);
        if (!secondskill) return firstskill;
    } //end else
    //interpolate between the two skills
    handle = BotInterpolateCharacters(firstskill, secondskill, skill);
    if (!handle) return 0;
    //write the character to the log file
    BotDumpCharacter(botcharacters[handle]);
    //
    return handle;
} //end of the function BotLoadCharacter
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int CheckCharacteristicIndex(int character, int index)
{
    bot_character_t *ch;

    ch = BotCharacterFromHandle(character);
    if (!ch) return qfalse;
    if (index < 0 || index >= MAX_CHARACTERISTICS)
    {
        botimport.Print(PRT_ERROR, "characteristic %d does not exist\n", index);
        return qfalse;
    } //end if
    if (!ch->c[index].type)
    {
        botimport.Print(PRT_ERROR, "characteristic %d is not initialized\n", index);
        return qfalse;
    } //end if
    return qtrue;
} //end of the function CheckCharacteristicIndex
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
float Characteristic_Float(int character, int index)
{
    bot_character_t *ch;

    ch = BotCharacterFromHandle(character);
    if (!ch) return 0;
    //check if the index is in range
    if (!CheckCharacteristicIndex(character, index)) return 0;
    //an integer will be converted to a float
    if (ch->c[index].type == CT_INTEGER)
    {
        return (float) ch->c[index].value.integer;
    } //end if
    //floats are just returned
    else if (ch->c[index].type == CT_FLOAT)
    {
        return ch->c[index].value._float;
    } //end else if
    //cannot convert a string pointer to a float
    else
    {
        botimport.Print(PRT_ERROR, "characteristic %d is not a float\n", index);
        return 0;
    } //end else if
    //	return 0;
} //end of the function Characteristic_Float
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
float Characteristic_BFloat(int character, int index, float min, float max)
{
    float value;
    bot_character_t *ch;

    ch = BotCharacterFromHandle(character);
    if (!ch) return 0;
    if (min > max)
    {
        botimport.Print(PRT_ERROR, "cannot bound characteristic %d between %f and %f\n", index, min, max);
        return 0;
    } //end if
    value = Characteristic_Float(character, index);
    if (value < min) return min;
    if (value > max) return max;
    return value;
} //end of the function Characteristic_BFloat
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int Characteristic_Integer(int character, int index)
{
    bot_character_t *ch;

    ch = BotCharacterFromHandle(character);
    if (!ch) return 0;
    //check if the index is in range
    if (!CheckCharacteristicIndex(character, index)) return 0;
    //an integer will just be returned
    if (ch->c[index].type == CT_INTEGER)
    {
        return ch->c[index].value.integer;
    } //end if
    //floats are casted to integers
    else if (ch->c[index].type == CT_FLOAT)
    {
        return (int) ch->c[index].value._float;
    } //end else if
    else
    {
        botimport.Print(PRT_ERROR, "characteristic %d is not a integer\n", index);
        return 0;
    } //end else if
    //	return 0;
} //end of the function Characteristic_Integer
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int Characteristic_BInteger(int character, int index, int min, int max)
{
    int value;
    bot_character_t *ch;

    ch = BotCharacterFromHandle(character);
    if (!ch) return 0;
    if (min > max)
    {
        botimport.Print(PRT_ERROR, "cannot bound characteristic %d between %d and %d\n", index, min, max);
        return 0;
    } //end if
    value = Characteristic_Integer(character, index);
    if (value < min) return min;
    if (value > max) return max;
    return value;
} //end of the function Characteristic_BInteger
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
void Characteristic_String(int character, int index, char *buf, int size)
{
    bot_character_t *ch;

    ch = BotCharacterFromHandle(character);
    if (!ch) return;
    //check if the index is in range
    if (!CheckCharacteristicIndex(character, index)) return;
    //an integer will be converted to a float
    if (ch->c[index].type == CT_STRING)
    {
        strncpy(buf, ch->c[index].value.string, size-1);
        buf[size-1] = '\0';
        return;
    } //end if
    else
    {
        botimport.Print(PRT_ERROR, "characteristic %d is not a string\n", index);
        return;
    } //end else if
    return;
} //end of the function Characteristic_String
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
void BotShutdownCharacters(void)
{
    int handle;

    for (handle = 1; handle <= MAX_CLIENTS; handle++)
    {
        if (botcharacters[handle])
        {
            BotFreeCharacter2(handle);
        } //end if
    } //end for
} //end of the function BotShutdownCharacters

